﻿using Microsoft.Extensions.DependencyInjection;
using System.Runtime.CompilerServices;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace IOP.Extension.DependencyInjection
{
    /// <summary>
    /// 自动注入服务扩展
    /// </summary>
    public static class AutowiredServiceExtensions
    {
        /// <summary>
        /// 函数表
        /// </summary>
        private static readonly ConcurrentDictionary<Type, Action<IServiceProvider, object>> _FuncTable = new ConcurrentDictionary<Type, Action<IServiceProvider, object>>();
        /// <summary>
        /// 同步锁
        /// </summary>
        private static readonly object _SyncLock = new object();

        /// <summary>
        /// 创建自动注入实例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        public static T CreateAutowiredInstance<T>(this IServiceProvider serviceProvider)
            where T : class
        {
            var type = typeof(T);
            var instance = ActivatorUtilities.CreateInstance<T>(serviceProvider);
            Action<IServiceProvider, object> action = GetAction(type);
            action(serviceProvider, instance);
            return instance;
        }

        /// <summary>
        /// 创建自动注入实例
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static object CreateAutowiredInstance(this IServiceProvider serviceProvider, Type type)
        {
            var instance = ActivatorUtilities.CreateInstance(serviceProvider, type);
            Action<IServiceProvider, object> action = GetAction(type);
            action(serviceProvider, instance);
            return instance;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static Action<IServiceProvider, object> GetAction(Type type)
        {
            if (!_FuncTable.TryGetValue(type, out Action<IServiceProvider, object> action))
            {
                lock (_SyncLock)
                {
                    action = CreateAutowiredAction(type);
                    _FuncTable.AddOrUpdate(type, action, (key, value) => value);
                }
            }
            return action;
        }

        /// <summary>
        /// 创建自动注入函数
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static Action<IServiceProvider, object> CreateAutowiredAction(Type type)
        {
            var serviceP = Expression.Parameter(typeof(IServiceProvider), "serv");
            var objP = Expression.Parameter(typeof(object), "obj");
            var convertObj = Expression.Convert(objP, type);
            var serviceType = typeof(IServiceProvider);
            MethodInfo getService = serviceType.GetMethod("GetService");
            List<Expression> expressions = new List<Expression>();
            foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            {
                var autowiredAttr = field.GetCustomAttribute<AutowiredAttribute>();
                if(autowiredAttr != null)
                {
                    var fieldExp = Expression.Field(convertObj, field);
                    var createService = Expression.Call(serviceP, getService, Expression.Constant(field.FieldType));
                    var setExp = Expression.Assign(fieldExp, Expression.Convert(createService, field.FieldType));
                    expressions.Add(setExp);
                }
            }
            foreach(var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            {
                var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>();
                if (autowiredAttr != null)
                {
                    var propExp = Expression.Property(convertObj, property);
                    var createService = Expression.Call(serviceP, getService, Expression.Constant(property.PropertyType));
                    var setExp = Expression.Assign(propExp, Expression.Convert(createService, property.PropertyType));
                    expressions.Add(setExp);
                }
            }
            var bodyExp = Expression.Block(expressions);
            var setAction = Expression.Lambda<Action<IServiceProvider, object>>(bodyExp, serviceP, objP).Compile();
            return setAction;
        }
    }
}
